package com.itliang.security.filter;


import com.alibaba.fastjson2.JSON;
import com.itliang.constant.RedisConstants;
import com.itliang.core.result.CodeEnum;
import com.itliang.core.result.ResponseCode;
import com.itliang.core.result.ResponseUtil;
import com.itliang.core.result.ResultUtils;
import com.itliang.service.system.SysMenuService;
import com.itliang.utils.JwtUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.itliang.constant.RedisConstants.*;


/**
 * 认证解析过滤器
 */
public class TokenAuthenticationFilter extends OncePerRequestFilter {

    private final String[] WhitelistPath = {"/api/admin/system/sysUser/login"};

    private StringRedisTemplate stringRedisTemplate;

    private SysMenuService sysMenuService;

    public TokenAuthenticationFilter(StringRedisTemplate stringRedisTemplate, SysMenuService sysMenuService) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.sysMenuService = sysMenuService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        String uri = request.getRequestURI();
        logger.info("request-uri:" + uri);
        //如果是登录接口，直接放行
        if (ArrayUtils.contains(WhitelistPath, uri)) {
            chain.doFilter(request, response);
            return;
        }

        String token = request.getHeader("token");
        if (!tokenCheck(token, response)) {
            return;
        }

        String userAccount = JwtUtils.getUserAccount(token);
        if (!permissionCheck(userAccount, response)) {
            return;
        }

        setAuthentication(userAccount);
        chain.doFilter(request, response);

    }

    /**
     * 设置认证信息到SecurityContextHolder
     *
     * @param userAccount 用户账号
     */
    private void setAuthentication(String userAccount) {


        // 获取权限信息
        List<SimpleGrantedAuthority> permission;
        String permissionKey = OPERATION_PERMISSION_KEY + userAccount;
        String permissionStr = stringRedisTemplate.opsForValue().get(permissionKey);
        // if (StringUtils.hasText(permissionStr)) { // redis中有权限
        List<Map> mapList = JSON.parseArray(permissionStr, Map.class);
        permission = new ArrayList<>();
        for (Map map : mapList) {
            permission.add(new SimpleGrantedAuthority((String) map.get("authority")));
        }

        UsernamePasswordAuthenticationToken authentication
                = new UsernamePasswordAuthenticationToken(userAccount, null, permission);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

    /**
     * 判断token是否非法
     *
     * @param token
     * @return
     */
    private boolean isValidToken(String token) {
        if (!StringUtils.hasText(token)) {
            return false;
        }
        if (!JwtUtils.checkToken(token)) {
            return false;
        }
        String userAccount = JwtUtils.getUserAccount(token);
        return Boolean.TRUE.equals(stringRedisTemplate.hasKey(LOGIN_TOKEN_KEY + userAccount));
    }

    /**
     * 判断token是否合法，与刷新redis中token过期时间
     *
     * @param token    令牌
     * @param response HttpServletResponse
     * @return true合法 false非法
     */
    private boolean tokenCheck(String token, HttpServletResponse response) {
        if (!StringUtils.hasText(token)) { // 请求未携带token
            ResponseUtil.out(response, ResultUtils.error(ResponseCode.NOT_LOGIN_ERROR, "请登录后再试"));

            return false;
        }
        if (!JwtUtils.checkToken(token)) { // token解析错误
            ResponseUtil.out(response, ResultUtils.error(ResponseCode.NOT_LOGIN_ERROR, "请登录后再试"));

            return false;
        }

        String userAccount = JwtUtils.getUserAccount(token);
        String tokenKey = RedisConstants.LOGIN_TOKEN_KEY + userAccount;
        Boolean hasKey = stringRedisTemplate.hasKey(tokenKey);
        if (Boolean.FALSE.equals(hasKey)) { // redis中token过期
            ResponseUtil.out(response, ResultUtils.error(CodeEnum.NOT_LOGIN_ERROR));
        }
        // 刷新redis中token有效时间
        stringRedisTemplate.expire(tokenKey, RedisConstants.LOGIN_TOKEN_TTL, TimeUnit.SECONDS);

        return true;
    }

    /**
     * redis中权限信息检查和刷新
     *
     * @param userAccount 用户账号
     * @param response    HttpServletResponse
     * @return true存在 false不存在
     */
    private boolean permissionCheck(String userAccount, HttpServletResponse response) {
        String permissionKey = OPERATION_PERMISSION_KEY + userAccount;
        // 刷新redis中权限信息有效时间
        stringRedisTemplate.expire(permissionKey, OPERATION_PERMISSION_TTL, TimeUnit.SECONDS);

        Boolean hasKey = stringRedisTemplate.hasKey(permissionKey);
        if (Boolean.FALSE.equals(hasKey)) {
            ResponseUtil.out(response, ResultUtils.error(1111, "您的权限已过期或被更改，请重新登录"));
            return false;
        }

        return true;
    }

}
